﻿/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 
*     ___                              __                 _       __              __     __       *
*    /   |   _____ ____   ___   _____ /_/____   ____     | |     / /____   _____ / /____/ /_____  *
*   / /| |  / ___// __ \ / _ \ / ___// // __ \ / __ \    | | /| / // __ \ / ___// // __  // ___/  *
*  / ___ | _\_ \ / /_/ //  __// /   / // /_/ // / / /    | |/ |/ // /_/ // /   / // /_/ /_\_ \    *
* /_/  |_|/____// ____/ \___//_/   /_/ \____//_/ /_/     |__/|__/ \____//_/   /_/ \____//____/    *
*              /_/                                                                                *
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SFML.Window;
using SFML.Graphics;

namespace Worlds.Media
{
    /// <summary>
    /// Graphics Window class, handles all things related to render windows and window events.
    /// </summary>
    public class GWindow
    {
        /// <summary>
        /// The window object, used for handling window events.
        /// </summary>
        private Window _Window { get; set; }
        /// <summary>
        /// The window object, used for handling window events.
        /// </summary>
        public Window Window { get { return this._Window; } }

        /// <summary>
        /// The render window object, used for rendering stuff.
        /// </summary>
        private RenderWindow _RenderWindow { get; set; }
        /// <summary>
        /// The render window object, used for rendering stuff.
        /// </summary>
        public RenderWindow RenderWindow { get { return this._RenderWindow; } }

        /// <summary>
        /// The render target object, used for object batching.
        /// </summary>
        private RenderTarget _Target { get; set; }
        /// <summary>
        /// The render target object, used for object batching.
        /// </summary>
        public RenderTarget Target { get { return this._Target; } }

        /// <summary>
        /// The view object, used for object batching.
        /// </summary>
        private View _View { get; set; }
        /// <summary>
        /// The view object, used for object batching.
        /// </summary>
        public View View { get; set; }

        /// <summary>
        /// The sprite object, used for object batching.
        /// </summary>
        private Sprite _Sprite { get; set; }
        /// <summary>
        /// The sprite object, used for object batching.
        /// </summary>
        public Sprite Sprite { get { return this._Sprite; } }

        /// <summary>
        /// The text object, used for object batching.
        /// </summary>
        private Text _Text { get; set; }
        /// <summary>
        /// The text object, used for object batching.
        /// </summary>
        public Text Text { get { return this._Text; } }

        /// <summary>
        /// Are we in a scene or not.
        /// </summary>
        private Boolean _InScene { get; set; }
        /// <summary>
        /// Are we in a scene or not.
        /// </summary>
        public Boolean InScene { get { return this._InScene; } }

        /// <summary>
        /// Constructs the graphics window.
        /// </summary>
        /// <param name="Handle">The object to bind the window to.</param>
        public GWindow(IntPtr Handle)
        {
            /* First thing we need is to initialize the RenderWindow object. */
            this._RenderWindow = new RenderWindow(Handle);

            /* Second thind we want to initialize is our Window object. */
            this._Window = new Window(Handle);

            /* Now we need to setup the View and RenderTarget. */
            this._View = new View();
            this._Target = this._RenderWindow;

            /* Initialize our sprite batching objects. */
            this._Sprite = new Sprite();
            this._Text = new Text();

            /* Now initialized the booleans. */
            this._InScene = false;
        }

        /// <summary>
        /// Stops the render window and clears out all resources.
        /// </summary>
        public void dispose()
        {
            /* Force the scene to end. */
            this.end();

            /* Unload the Window object. */
            if (this._Window != null)
            {
                this._Window.Close();
                this._Window = null;
            }

            /* Unload the RenderWindow object. */
            if (this._RenderWindow != null)
            {
                this._RenderWindow.Close();
                this._RenderWindow = null;
            }

            /* Unload the sprite batcher objects. */
            this._Target = null;
            this._View = null;
            this._Text = null;
            this._Sprite = null;

            /* Collect the garbage. */
            GC.Collect();
        }

        /// <summary>
        /// Begins a new scene for the render window.
        /// </summary>
        public void begin()
        {
            /* If for some ungodly reason the render window is not loaded throw an error. */
            if (this._RenderWindow == null || this._RenderWindow.IsOpen())
                throw new NullReferenceException("Error starting new scene, reason: The window is not open.");

            /* If we are already in a scene end it. */
            if (this._InScene)
                this.end();

            /* Clear out the window for starters. */
            this._RenderWindow.Clear();

            /* Now reset the view. */
            this._View.Reset(new FloatRect(0, 0, this._RenderWindow.Size.X, this._RenderWindow.Size.Y));
            this._Target.SetView(this._View);
        }

        /// <summary>
        /// Ends the current scene and displays it on the window.
        /// </summary>
        public void end()
        {
            /* If for some ungodly reason the render window is not loaded throw an error. */
            if (this._RenderWindow == null || this._RenderWindow.IsOpen())
                throw new NullReferenceException("Error ending current scene, reason: The window is not open.");

            /* Make sure we are in a scene. */
            if (this._InScene)
            {
                /* Display the scene. */
                this._RenderWindow.Display();
                this._InScene = false;

                /* Dispatch the events our Window object has. */
                this._Window.DispatchEvents();
            }
        }


        /// <summary>
        /// Attempts to render an image texture onto the render window.
        /// </summary>
        /// <param name="Image">The image to render.</param>
        /// <param name="Settings">The image settings.</param>
        public void drawImage(Texture Image, GImageSettings Settings)
        {
            /* Make sure that we are in a scene. */
            if (!this.InScene)
                return;

            /* Update the texture image to the one we wnat rendered. */
            this._Sprite.Texture = Image;

            /* Now we get to adjust the settings. */
            this._Sprite.Position = Settings.Position;
            this._Sprite.Scale = Settings.Scale;
            this._Sprite.TextureRect = Settings.Source;
            this._Sprite.Color = Settings.Colour;

            /* Now draw the image. */
            this._Sprite.Draw(this._Target, Settings.States);
        }

        /// <summary>
        /// Attempts to draw a line of text to the render window.
        /// </summary>
        /// <param name="TextLine">The line of text to draw.</param>
        /// <param name="Font">The font to use.</param>
        /// <param name="Settings">The text settings.</param>
        public void drawText(String TextLine, Text Font, GTextSettings Settings)
        {
            /* Make sure that we are in a scene. */
            if (!this.InScene)
                return;

            /* Draw the shadow. */
            Boolean Shadow = true;
            if (Shadow)
            {
                /* Cache our origional settings. */
                Color Colour = Settings.Colour;
                Vector2f Position = Settings.Position;

                /* Update them for the shadow. */
                Settings.setPosition(Position.X + 1, Position.Y + 1);
                Settings.setColour(new Color(0, 0, 0));

                /* Now draw with the modified settings. */
                this.drawText(TextLine, Text, Settings, false);

                /* Now change the settings back. */
                Settings.setPosition(Position.X, Position.Y);
                Settings.setColour(Colour);
            }

            /* Update the text to the one we want rendered. */
            this._Text = Text;

            /* Now we get to adjust the settings. */
            this._Text.DisplayedString = TextLine;
            this._Text.Position = Settings.Position;
            this._Sprite.Color = Settings.Colour;

            /* Now draw the image. */
            this._Text.Draw(this._Target, Settings.States);
        }

        /// <summary>
        /// Attempts to draw a line of text to the render window.
        /// </summary>
        /// <param name="TextLine">The line of text to draw.</param>
        /// <param name="Font">The font to use.</param>
        /// <param name="Settings">The text settings.</param>
        /// <param name="Shadow">Shadow rendering?</param>
        public void drawText(String TextLine, Text Font, GTextSettings Settings, Boolean Shadow)
        {
            /* Make sure that we are in a scene. */
            if (!this.InScene)
                return;

            /* Do we want to draw a shadow? */
            if (Shadow)
            {
                /* Cache our origional settings. */
                Color Colour = Settings.Colour;
                Vector2f Position = Settings.Position;

                /* Update them for the shadow. */
                Settings.setPosition(Position.X + 1, Position.Y + 1);
                Settings.setColour(new Color(0, 0, 0));

                /* Now draw with the modified settings. */
                this.drawText(TextLine, Text, Settings, false);
                
                /* Now change the settings back. */
                Settings.setPosition(Position.X, Position.Y);
                Settings.setColour(Colour);
            }

            /* Update the text to the one we want rendered. */
            this._Text = Text;

            /* Now we get to adjust the settings. */
            this._Text.DisplayedString = TextLine;
            this._Text.Position = Settings.Position;
            this._Sprite.Color = Settings.Colour;

            /* Now draw the image. */
            this._Text.Draw(this._Target, Settings.States);
        }


        /// <summary>
        /// Returns the current width of the RenderWindow.
        /// </summary>
        public UInt32 Width
        {
            get
            {
                /* Check that the SFML Window is already initialized. */
                if (this._RenderWindow == null)
                    return 0;

                /* Return the width. */
                return this._RenderWindow.Size.X;
            }
        }

        /// <summary>
        /// Returns the current height of the RenderWindow.
        /// </summary>
        public UInt32 Height
        {
            get
            {
                /* Check that the SFML Window is already initialized. */
                if (this._RenderWindow == null)
                    return 0;

                /* Return the width. */
                return this._RenderWindow.Size.Y;
            }
        }
    }
}
